/*
 * COPYRIGHT:	2010 Christopher Smith, licenced under the GNU GPL v2; see COPYING in the top level directory
 * PROJECT:		sdedit
 * FILE:		workqueue.cpp
 * PURPOSE:		thread work queue interfaces and common support
 *				
 */

namespace WorkQueue
{

HANDLE Queue;
HANDLE WorkThread;
BOOLEAN Quitting;

//KQUEUE work items where
//	'CompletionKey' is a pointer to the WorkItem, which is owned by the queue until completed or deleted (cancelled)

struct WorkItem
{
	virtual ~WorkItem() {}
	virtual void Work() = 0;
	virtual void Complete() = 0;
	virtual void CompleteExcept(EXCEPTION_RECORD const *ExceptionRecord) = 0;
};

void PostWorkItem(WorkItem *wi)
{
	RaiseOnWin32ErrorT(PostQueuedCompletionStatus(Queue, NULL, reinterpret_cast<ULONG_PTR>(wi), NULL));
}

template <class T> struct LookupWorkItem : WorkItem
{
	auto_refct<Lookup::CompletionTarget<T> > Ct;
	auto_szpod<T> Result;

	LookupWorkItem(Lookup::CompletionTarget<T> *Ct) :
		Ct(Ct)
	{}

	virtual void Complete() override
	{
		Ct->Complete(Result);
		delete this;
	}

	virtual void CompleteExcept(EXCEPTION_RECORD const *ExceptionRecord) override
	{
		Ct->CompleteExcept(ExceptionRecord);
		delete this;
	}
};

long HandlePushableWorkItemExceptions(unsigned long ExceptionCode, EXCEPTION_POINTERS *ExceptionPointers, StoredException &Se)
{
	if(ExceptionCode == STATUS_COUNT_CANCEL_HANDLERS)
		return EXCEPTION_CONTINUE_EXECUTION;

	if(IsExpectedException(ExceptionCode))
	{
		Se = ExceptionPointers->ExceptionRecord;
		return EXCEPTION_EXECUTE_HANDLER;
	}
	else
		return EXCEPTION_CONTINUE_SEARCH;
}

void ExecuteWorkItemSEH(WorkItem *wi, StoredException &Se)
{
	__try
	{
		wi->Work();
		wi->Complete();
	}
	__except(HandlePushableWorkItemExceptions(exception_code(), exception_info(), Se))
	{
		wi->CompleteExcept(Se);
	}
}

void CancelWorkItemSEH(WorkItem *wi, StoredException &Se)
{
	__try
	{
		RaiseCancelAll();
	}
	__except(HandlePushableWorkItemExceptions(exception_code(), exception_info(), Se))
	{
		wi->CompleteExcept(Se);
	}
}

DWORD WINAPI Worker(PVOID Parameter)
{
	SdEditThreadResources SdeTr;
	UNREFERENCED_PARAMETER(Parameter);

	WorkItem *wi;
	DWORD Extra1;
	LPOVERLAPPED Extra2;

	while(GetQueuedCompletionStatus(Queue, &Extra1, reinterpret_cast<ULONG_PTR *>(&wi), &Extra2, Quitting ? 0 : INFINITE))
	{
		StoredException Se;

		if(Quitting)
		{
			if(wi)
				CancelWorkItemSEH(wi, Se);
		}
		else
			ExecuteWorkItemSEH(wi, Se);
	}

	return 0;
}

void Init()
{
	Quitting = FALSE;

	Queue = RaiseOnWin32ErrorT(CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0));

	ULONG ThreadId;

	WorkThread = RaiseOnWin32ErrorT(CreateThread(NULL, 0x4000, &Worker, NULL, 0, &ThreadId));
}

void Cleanup()
{
	Quitting = TRUE;

	RaiseOnWin32ErrorT(PostQueuedCompletionStatus(Queue, NULL, NULL, NULL));

	//if you're getting a deadlock here, check to see if WorkThread is blocked on the loader lock which this thread is holding
	//there's a reason that GuiLibResources is maintained independently of the module itself: there are things that can't be done during module load/unload
	//someone didn't release their reference to GuiLibResources properly
	WaitForSingleObject(WorkThread, INFINITE);

	CloseHandle(WorkThread);
}

}//namespace WorkQueue